<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2016/10/9
 * Time: 11:39
 */

namespace common\utils;

use Yii;
class RefundUtils
{
    protected $SSLCERT_PATH;//证书路径
    protected $SSLKEY_PATH;//证书路径
    protected $key;//商户支付密钥

    /**初始化退款类需要的变量**/
    function __construct($app_id, $mch_id, $key, $outRefundNo, $out_trade_no, $totalFee, $refundFee, $notify_url)
    {
        $this->app_id       = $app_id;
        $this->mch_id       = $mch_id;//商户号
        $this->outRefundNo  = $outRefundNo;//自己订单后台升成的退款单号
        $this->out_trade_no = $out_trade_no;//商户系统内部订单号，要求32个字符内，只能是数字、大小写字母_-|*@ ，且在同一个商户号下唯一。
        $this->totalFee     = $totalFee * 100;//微信以一分为单位,系统以元为单位故乘以100,订单总金额
        $this->refundFee    = $refundFee * 100;//微信以一分为单位,系统以元为单位故乘以100,此次退款退的总金额
        $this->key          = $key;//appsecret ,商户号的秘钥
        $this->notify_url   = $notify_url;
        $this->SSLCERT_PATH = dirname(Yii::$app->BasePath).'\\'.'uploads\\cert\\apiclient_cert.pem';//证书在服务器中的绝对路径
        $this->SSLKEY_PATH  = dirname(Yii::$app->BasePath).'\\'.'uploads\\cert\\apiclient_key.pem';//证书在服务器中的绝对路径
    }

    /**对外暴露的退款接口**/
    public function refundStart()
    {
        $result = $this->wxrefundapi();
        return $result;
    }

    /**通过微信api进行退款流程**/
    private function wxrefundapi()
    {
    $parma  = array(
        'appid' => $this->app_id,
        'mch_id' => $this->mch_id,//商户号
        'nonce_str' => $this->createNoncestr(32),
        'out_refund_no' => $this->outRefundNo,
        'out_trade_no' => $this->out_trade_no,
        'refund_fee' => $this->refundFee,
        'total_fee' => $this->totalFee,
        'notify_url'=>$this->notify_url,//回调地址
     );
        $parma['sign'] = $this->getSign($parma);
        $xmldata   = $this->arrayToXml($parma);
        $xmlresult = $this->postXmlSSLCurl($xmldata, 'https://api.mch.weixin.qq.com/secapi/pay/refund');
        $result    = $this->xmlToArray($xmlresult);
        return $result;
    }

    /**需要使用证书的请求**/
    function postXmlSSLCurl($xml, $url, $second = 30)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_TIMEOUT, $second); //超时时间
        //这里设置代理，如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);//设置header
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);//要求结果为字符串且输出到屏幕上
        //设置证书
        //使用证书：cert 与 key 分别属于两个.pem文件
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLCERT, $this->SSLCERT_PATH);
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLKEY, $this->SSLKEY_PATH);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            echo "curl出错，错误码:$error" . "<br>";
            curl_close($ch);
            return false;
        }
    }

    /**产生32位随机字符串**/
    private function createNoncestr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str   = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }
    
    /**生成签名**/
    private function getSign($Obj)
    {
        foreach ($Obj as $k => $v) {
            $Parameters[$k] = $v;
        }
        //签名步骤一：按字典序排序参数
        ksort($Parameters);
        $String = $this->formatBizQueryParaMap($Parameters, false);
        //签名步骤二：在string后加入KEY
        $String = $String . "&key=" . $this->key;
        //签名步骤三：MD5加密
        $String = md5($String);
        //签名步骤四：所有字符转为大写
        $result_ = strtoupper($String);
        return $result_;
    }

    /**数组转换成xml**/
    private function arrayToXml($arr)
    {
        $xml = "<xml>";
        foreach ($arr as $key => $val) {
        if (is_array($val)) {
            $xml .= "<" . $key . ">" . arrayToXml($val) . "</" . $key . ">";
        } else {
            $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
        }
     }
        $xml .= "</xml>";
        return $xml;
    }

     /**xml转换成数组**/
    private function xmlToArray($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $val = json_decode(json_encode($xmlstring), true);
        return $val;
    }

    /**格式化参数，签名过程需要使用**/
    private function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
        if ($urlencode) {
            $v = urlencode($v);
        }
         $buff .= $k . "=" . $v . "&";
      }
     $reqPar = '';
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }
    /*
     * 微信支付退款回调解密(req_info)
     * 解密步骤如下：
     * 1）对加密串A做base64解码，得到加密串B
     * 2）对商户key做md5，得到32位小写key* ( key设置路径：微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
     * 3）用key*对加密串B做AES-256-ECB解密（PKCS7Padding）
     * url地址：https://segmentfault.com/q/1010000019927589/
     */
    private function refundDecrypt($str) {
        $decrypt = base64_decode($str,true);
        $key = md5( $this->key);//$key商户秘钥
        return openssl_decrypt($decrypt , 'aes-256-ecb',$key, OPENSSL_RAW_DATA);
    }
}
